import { Record } from 'immutable';
import pick from 'lodash/pick';
import ACTION from '../constant/action-type';

const DEFAULTS = {
  value: undefined,
  type: undefined,
  selection: undefined,
  path: undefined,
  node: undefined,
  source: undefined,
  target: undefined,
  index: undefined,
  properties: undefined
};

const ACTION_ATTR = {
  [ACTION.SET_SELECTION]: ['selection'],
  [ACTION.INSERT_NODE]: ['node', 'path'],
  [ACTION.REMOVE_NODE]: ['path', 'node'],
  [ACTION.MOVE_NODE]: ['source', 'target'],
  [ACTION.UPDATE_NODE]: ['node', 'properties'],
  [ACTION.UPDATE_VALUE]: ['value', 'properties'],
  [ACTION.SET_VALUE]: ['value']
};

class Action extends Record(DEFAULTS) {
  static create(attr = {}) {
    return Action.formJSON(attr);
  }

  static formJSON(object = {}) {
    if (Action.isAction(object)) {
      return object;
    }
    const { type, value } = object;
    const attrs = ACTION_ATTR[type].reduce(
      (attr, key) => {
        const result = attr;
        result[key] = object[key];
        return result;
      },
      { type, value }
    );
    return new Action(attrs);
  }

  apply(value, schema, editor) {
    const { type } = this;
    switch (type) {
      case ACTION.SET_VALUE:
        return this.value;
      case ACTION.UPDATE_VALUE:
        return value.merge(this.properties);
      case ACTION.SET_SELECTION:
        return value.setSelection(this.selection);
      case ACTION.INSERT_NODE:
        return value.insertNode(
          this.path,
          schema.normalizeNode(this.node, editor)
        );
      case ACTION.REMOVE_NODE:
        return value.removeNode(this.path);
      case ACTION.MOVE_NODE:
        return value.moveNode(this.source, this.target);
      case ACTION.UPDATE_NODE:
        const normalizedNode = schema.normalizeNode(
          this.node.merge(this.properties),
          editor
        );
        return value.updateNode(this.node.key, normalizedNode);
      default:
        break;
    }
  }

  revert() {
    const { type } = this;
    switch (type) {
      case ACTION.UPDATE_VALUE:
        const value = this.value.merge(this.properties);
        const revertProperties = pick(this.value, Object.keys(this.properties));
        return this.set('value', value).set('properties', revertProperties);
      case ACTION.INSERT_NODE:
        return this.set('type', ACTION.REMOVE_NODE);
      case ACTION.REMOVE_NODE:
        return this.set('type', ACTION.INSERT_NODE);
      case ACTION.MOVE_NODE:
        return this.set('type', ACTION.UPDATE_VALUE).set(
          'properties',
          pick(this.value, ['document', 'style', 'data'])
        );
      case ACTION.UPDATE_NODE:
        const node = this.node.merge(this.properties);
        const properties = pick(this.node, Object.keys(this.properties));
        return this.set('node', node).set('properties', properties);
      default:
        break;
    }
  }

  get canUndo() {
    switch (this.type) {
      case ACTION.SET_SELECTION:
        return false;
      default:
        return true;
    }
  }
}

Action.prototype.__action__ = true;
export default Action;
